package com.shihan.lock;

import com.shihan.utils.RedisUtils;
import org.apache.commons.lang3.StringUtils;
import redis.clients.jedis.Jedis;

import java.util.UUID;

/**
 * @Description Redis 实现分布式锁
 * @Author shihan
 * @Date 2021/1/26 18:14
 **/
public class RedisLock {

    public static final int LOCK_SUCCESS = 1;

    /**
     * @param lockKey      创建锁时的唯一标识
     * @param retryTimeOut 竞争锁的超时时间（单位：毫秒）
     * @param timeOut      key 的生命周期时长，超过生命周期时长后，自动销毁（单位：毫秒）
     * @return 分布式锁创建成功后的 lockValue
     */
    public String getLock(String lockKey, int retryTimeOut, int timeOut) {
        // 1、获取 jedis 客户端；
        Jedis jedis = null;

        try {
            jedis = RedisUtils.getJedis();

            // 2、判断是否超过竞争锁的超时时间；
            long endTime = System.currentTimeMillis() + retryTimeOut;
            while (System.currentTimeMillis() < endTime) {
                // 分布式锁的 value 很重要：配合后面释放锁时使用，用来保证当前释放的锁是自己的那个（防止释放了别人加的锁）
                String lockValue = UUID.randomUUID().toString();
                // setnx 返回值：1-成功，0-失败
                if (jedis.setnx(lockKey, lockValue) == LOCK_SUCCESS) {
                    // 设置 key 的超时时间（expire的第二个参数，单位是秒，所以要将毫秒值转换为秒）
                    jedis.expire(lockKey, timeOut / 1000);
                    return lockValue;
                }
                // setnx 没成功的，如果还在超时时间之内，则继续尝试获取锁；
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 3、释放 jedis 连接；
            if (jedis != null) {
                jedis.close();
            }
        }

        return null;
    }

    /**
     * 释放锁
     * 加上 lockValue 的原因：
     * 1、为了防止异常情况，比如宕机导致释放锁代码未执行；
     * 2、重新启动后，会无法重新释放之前的锁；
     * 解决办法：
     * 1、使用 lockValue；
     * 2、给 key 设置生命周期时间；
     *
     * @param lockKey
     * @param lockValue
     * @return
     */
    public boolean unlock(String lockKey, String lockValue) {
        // 1、获取 jedis 客户端；
        Jedis jedis = null;

        try {
            jedis = RedisUtils.getJedis();

            // 2、获取锁对应的 value 值
            String getValue = jedis.get(lockKey);
            // 3、判断 value 值 和当前的 lockValue 是否相同，相同才释放锁成功
            if (StringUtils.isNoneBlank(getValue) && getValue.equals(lockValue)) {
                // 最终删除成功，才返回 true
                if (jedis.del(lockKey) > 0) {
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 4、释放锁
            if (jedis != null) {
                jedis.close();
            }
        }

        return false;
    }


}
